/**
 * Copyright (C) 2008 by The Regents of the University of California
 * Redistribution of this file is permitted under the terms of the GNU
 * Public License (GPL).
 *
 * @author Junghoo "John" Cho <cho AT cs.ucla.edu>
 * @date 3/24/2008
 */

#include <iostream>
#include <cstdio>
#include <iostream>
#include <fstream>
//#include <vector>
//#include "Bruinbase.h"
#include "SqlEngine.h"
#include "BTreeIndex.h"

struct key_rid {
    int key;
    RecordId rid;
};


using namespace std;

// external functions and variables for load file and sql command parsing 
extern FILE* sqlin;
int sqlparse(void);

RC SqlEngine::run(FILE* commandline) {
    fprintf(stdout, "Bruinbase> ");

    // set the command line input and start parsing user input
    sqlin = commandline;
    sqlparse(); // sqlparse() is defined in SqlParser.tab.c generated from
    // SqlParser.y by bison (bison is GNU equivalent of yacc)

    return 0;
}

RC SqlEngine::select(int attr, const string& table, const vector<SelCond>& cond) {
    RecordFile rf; // RecordFile containing the table
    RecordId rid; // record cursor for table scanning
    PageId leftMostId;
    RC rc;
    IndexCursor cursor, moving_cursor;
    int key, flag;
    string value;
    int count;
    int diff, counter=0;
    ofstream myFile;

    cursor.eid = 0;
    cursor.pid = 0;
    
    vector<key_rid> retrieved;
    vector<SelCond> notequals;
    vector<SelCond> valuecond;
    int keycond = 0, norecords = 0, cho_code_execute = 1, less_than_first_key = 0;
    BTreeIndex btindex((char*)table.c_str());

    // open the table file
    if ((rc = rf.open(table + ".tbl", 'r')) < 0) {
        fprintf(stderr, "Error: table %s does not exist\n", table.c_str());
        return rc;
    }
    /**
     * OUR CODE:
     * open the index file here.
     */
    
    char* indexname = (char*) malloc(sizeof (table.c_str()) + 5);
    strcpy(indexname, table.c_str());
    strcat(indexname, ".idx");
    
    
    ifstream mf;
    mf.open(indexname, ios::in);
    if ((rc = btindex.getPageFile().open(indexname, 'r')) < 0) {
        
        goto cho_code;
    }

    
    if(attr == 4 && cond.size() == 0)
{
        

        cho_code_execute=0;
        leftMostId =  btindex.getRootPid();
        
        btindex.gotoLeftMost(leftMostId);

        moving_cursor.pid = leftMostId;
        moving_cursor.eid = 1;
        while (1) {

            btindex.readForward(moving_cursor, key, rid);
            counter++;
            if (moving_cursor.pid == -1) {

                struct key_rid temp;
                temp.key = key;
                temp.rid = rid;
 
                break;
            }
        }
        
    }
    
    
    for (unsigned i = 0; i < cond.size(); i++) {
        less_than_first_key = 0;
        if (cond[i].attr == 2 && cond[i].comp != cond[i].NE) {
            valuecond.push_back(cond[i]);
            continue;
        } else if ((cond[i].attr == 1 && cond[i].comp == cond[i].NE) ||
                (cond[i].attr == 2 && cond[i].comp == cond[i].NE)) { //key != OR value!=
            notequals.push_back(cond[i]);
        }
        else {
            
            if (retrieved.size() == 0 && keycond++ == 0) {
                
                cho_code_execute = 0;
                //index lookup!

                flag = btindex.locate(atoi(cond[i].value), cursor);


                switch (cond[i].comp) {
                    case SelCond::EQ:
                        if (flag == EQUALITY_NOT_SATISFIED || flag == EQUALITY_NOT_SATISFIED_OVERFLOW) {
                            break;
                        } else {
                            key_rid temp;
                            
                            btindex.readForward(cursor, key, rid);
                            
                            temp.key = key;
                            temp.rid = rid;
                            retrieved.push_back(temp);
                                    

                        }

                        break;
                    case SelCond::GT:
                        if (cursor.pid == -1) {
                            norecords = 1;
                            break;
                        } else {

                            moving_cursor = cursor;
                            while (moving_cursor.pid != -1) {
                                btindex.readForward(moving_cursor, key, rid);
                                if (key == atoi(cond[i].value))
                                    continue;

                                struct key_rid temp;
                                temp.key = key;
                                temp.rid = rid;
                                retrieved.push_back(temp);

                            }
                        }
                        break;
                    case SelCond::LT:
                        leftMostId = btindex.getRootPid();
                        btindex.gotoLeftMost(leftMostId);

                        moving_cursor.pid = leftMostId;
                        moving_cursor.eid = 1;

                        while (moving_cursor.pid != cursor.pid ||
                              (moving_cursor.pid == cursor.pid && 
                                moving_cursor.eid < cursor.eid)) {
                            
                            
                            btindex.readForward(moving_cursor, key, rid);
                            
                            if(moving_cursor.pid == -1) {
                                
                                struct key_rid temp;
                                temp.key = key;
                                temp.rid = rid;

                                retrieved.push_back(temp);
                                break;
                            }
//                            if(key == atoi(cond[i].value))
//                                    break;                          //cond[i].value=40  ||10||20||30||---->||40||

                            if (less_than_first_key++ == 0) {
                                if (key > atoi(cond[i].value)) {
                                    norecords = 1;
                                    break;
                                }
                            }

                            struct key_rid temp;
                            temp.key = key;
                            temp.rid = rid;
                            
                            retrieved.push_back(temp);

                        }
                        break;
                    case SelCond::GE:
                        if (cursor.pid == -1) {
                            norecords = 1;
                            break;
                        } else {
                            moving_cursor = cursor;
                            while (moving_cursor.pid != -1) {
                                btindex.readForward(moving_cursor, key, rid);

                                struct key_rid temp;
                                temp.key = key;
                                temp.rid = rid;
                                retrieved.push_back(temp);

                            }
                        }
                        break;

                    case SelCond::LE:

                        leftMostId = btindex.getRootPid();
                        btindex.gotoLeftMost(leftMostId);

                        moving_cursor.pid = leftMostId;
                        moving_cursor.eid = 1;

                        while (moving_cursor.pid != cursor.pid ||
                                (moving_cursor.pid == cursor.pid && moving_cursor.eid <= cursor.eid) ||
                                (moving_cursor.pid != -1)) {
                            btindex.readForward(moving_cursor, key, rid);
                            if (key > atoi(cond[i].value))
                                break; // key=45 and ||10||20||30||---->||40||50||

                            if (less_than_first_key++ == 0) {
                                if (key > atoi(cond[i].value)) {
                                    norecords = 1;
                                    break;
                                }
                            }

                            struct key_rid temp;
                            temp.key = key;
                            temp.rid = rid;
                            retrieved.push_back(temp);

                        }
                        break;
                }


            } else {

                int val = atoi(cond[i].value);
                int comparator = cond[i].comp;
                int a, x;
                int size = retrieved.size();
                for (a = 0, x = 0; a < size; a++, x++) {
                    switch (cond[i].comp) {
                        case SelCond::EQ:
                            if (retrieved[x].key != val) {
                                retrieved.erase(retrieved.begin() + x);
                                x--;

                            }

                            break;
                        case SelCond::GT:
                            if (retrieved[x].key <= val) {
                                retrieved.erase(retrieved.begin() + x);
                                x--;
                            }
                            break;
                        case SelCond::LT:
                            if (retrieved[x].key >= val) {
                                retrieved.erase(retrieved.begin() + x);
                                x--;
                            }
                            break;
                        case SelCond::GE:
                            if (retrieved[x].key < val) {
                                retrieved.erase(retrieved.begin() + x);
                                x--;
                            }
                            break;
                        case SelCond::LE:
                            if (retrieved[x].key > val) {
                                retrieved.erase(retrieved.begin() + x);
                                x--;
                            }
                            break;
                    }

                }
            }

            if (retrieved.size() == 0) {
                norecords = 1;
                break;
            }
        }

    }


    if (norecords == 1) {
        fprintf(stdout, "No Records Found\n");
    }
    else if (valuecond.size() != 0) {
        
        //further filter based on VALUE conditions
        int retrieved_size = retrieved.size();
        for (int j = 0, x = 0; j < retrieved_size; j++, x++) {
            rf.read(retrieved[x].rid, key, value);
            
            int not_satisfied = 0;
            for (int a = 0; a < valuecond.size(); a++) {

                string val = valuecond[a].value;
                
                switch (valuecond[a].comp) {
                    case SelCond::EQ:
                        if (strcmp(value.c_str(), val.c_str())!=0) {
                            retrieved.erase(retrieved.begin() + x);
                            x--;

                        }

                        break;
                    case SelCond::GT:
                        if (value <= val) {
                            retrieved.erase(retrieved.begin() + x);
                            x--;
                        }
                        break;
                    case SelCond::LT:
                        if (value >= val) {
                            retrieved.erase(retrieved.begin() + x);
                            x--;
                        }
                        break;
                    case SelCond::GE:
                        if (value < val) {
                            retrieved.erase(retrieved.begin() + x);
                            x--;
                        }
                        break;
                    case SelCond::LE:
                        if (value > val) {
                            retrieved.erase(retrieved.begin() + x);
                            x--;
                        }
                        break;
                }

            }

        }
    }

    if (notequals.size() != 0) {
        
        int retrieved_size = retrieved.size();
        for (int j = 0, x = 0; j < retrieved_size; j++, x++) {
            rf.read(retrieved[x].rid, key, value);
            for (int i = 0; i < notequals.size(); i++) {
                if (notequals[i].attr == 1) {
                    if (key == atoi(notequals[i].value)) {
                        retrieved.erase(retrieved.begin() + x);
                        x--;
                        break;
                    }

                } else if (notequals[i].attr == 2) {
                    if (strcmp(value.c_str(), notequals[i].value)==0) {
                        retrieved.erase(retrieved.begin() + x);
                        x--;
                        break;
                    }
                }
            }
        }
    }

    //print the results ---- TRY TO OPTIMIZE THIS
    if (cho_code_execute == 0) {
        
        
        for (int j = 0; j < retrieved.size(); j++) {

            rf.read(retrieved[j].rid, key, value);
            switch (attr) {
                case 1: // SELECT key
                    fprintf(stdout, "%d\n", key);
                    break;
                case 2: // SELECT value
                    fprintf(stdout, "%s\n", value.c_str());
                    break;
                case 3: // SELECT *
                    fprintf(stdout, "%d '%s'\n", key, value.c_str());
                    break;
            }
        }

        if (attr == 4){
            
            if(cond.size()!=0)
            fprintf(stdout, "%d\n", (int) retrieved.size());
            else
            fprintf(stdout, "%d\n", counter);
        }
            

    } else {
cho_code:

        rid.pid = rid.sid = 0;
        count = 0;
        while (rid < rf.endRid()) {
            // read the tuple
            if ((rc = rf.read(rid, key, value)) < 0) {
                fprintf(stderr, "Error: while reading a tuple from table %s\n", table.c_str());
                goto exit_select;
            }

            // check the conditions on the tuple
            for (unsigned i = 0; i < cond.size(); i++) {
                // compute the difference between the tuple value and the condition value
                switch (cond[i].attr) {
                    case 1:
                        diff = key - atoi(cond[i].value);
                        break;
                    case 2:
                        diff = strcmp(value.c_str(), cond[i].value);
                        break;
                }

                // skip the tuple if any condition is not met
                switch (cond[i].comp) {
                    case SelCond::EQ:
                        if (diff != 0) goto next_tuple;
                        break;
                    case SelCond::NE:
                        if (diff == 0) goto next_tuple;
                        break;
                    case SelCond::GT:
                        if (diff <= 0) goto next_tuple;
                        break;
                    case SelCond::LT:
                        if (diff >= 0) goto next_tuple;
                        break;
                    case SelCond::GE:
                        if (diff < 0) goto next_tuple;
                        break;
                    case SelCond::LE:
                        if (diff > 0) goto next_tuple;
                        break;
                }
            }

            // the condition is met for the tuple. 
            // increase matching tuple counter
            count++;

            // print the tuple 
            switch (attr) {
                case 1: // SELECT key
                    fprintf(stdout, "%d\n", key);
                    break;
                case 2: // SELECT value
                    fprintf(stdout, "%s\n", value.c_str());
                    break;
                case 3: // SELECT *
                    fprintf(stdout, "%d '%s'\n", key, value.c_str());
                    break;
            }

            // move to the next tuple
next_tuple:
            ++rid;
        }

        // print matching tuple count if "select count(*)"
        if (attr == 4) {
            fprintf(stdout, "%d\n", count);
        }
        rc = 0;
    }
    // close the table file and return
exit_select:
    rf.close();
    btindex.getPageFile().close();
    mf.close();
    
    return rc;
}

/**
 * Load Function: To load populate the RecordFile structure
 * and load data onto the file pointed to by RecordFile
 * 
 * @param table
 * @param loadfile
 * @param index
 * @return 0 on success
 */
RC SqlEngine::load(const string& table, const string& loadfile, bool index) {
    /* your code here */

    BTreeIndex btindex((char*)table.c_str());
    RecordFile rf;
    ifstream loadFile(loadfile.c_str());
    ofstream myFile;
    ofstream metadata;
    string line, value;
    int key, recid = 0;
    
    char* fulltablename = (char*) malloc(sizeof (table.c_str()) + 5);
    strcpy(fulltablename, table.c_str());
    strcat(fulltablename, ".tbl");

    myFile.open(fulltablename, ios::app);
    metadata.open("metadata.txt", ios::app);
    rf.open(fulltablename, 'w');

//    if(index == true)
//    {
//    const string filename = "index.idx";
//    btindex.getPageFile().open(filename, ios::app);
//    }
    
    int cnt = 1;
    while (loadFile.good()) {
        
        getline(loadFile, line);
        parseLoadLine(line, key, value);
        if(line=="")
            continue;

        const string temp = value;
        int k = key;
        RecordId rid = rf.endRid();
        
        metadata << "PageID: " << rf.endRid().pid << "Slot ID: " << rf.endRid().sid;
        rf.append(k, temp, rid);

        if(index==true){  
            btindex.insert(key, rid);
        }
        
        

    }


    loadFile.close();
    myFile.close();
    metadata.close();

    return 0;
}

RC SqlEngine::parseLoadLine(const string& line, int& key, string& value) {
    const char *s;
    char c;
    string::size_type loc;

    // ignore beginning white spaces
    c = *(s = line.c_str());
    while (c == ' ' || c == '\t') {
        c = *++s;
    }

    // get the integer key value
    key = atoi(s);

    // look for comma
    s = strchr(s, ',');
    if (s == NULL) {
        return RC_INVALID_FILE_FORMAT;
    }

    // ignore white spaces
    do {
        c = *++s;
    } while (c == ' ' || c == '\t');

    // if there is nothing left, set the value to empty string
    if (c == 0) {
        value.erase();
        return 0;
    }

    // is the value field delimited by ' or "?
    if (c == '\'' || c == '"') {
        s++;
    } else {
        c = '\n';
    }

    // get the value string
    value.assign(s);
    loc = value.find(c, 0);
    if (loc != string::npos) {
        value.erase(loc);
    }

    
    return 0;
}
